using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Reflection.Emit;
using System.Text;

namespace Multiplicity.Runtime.Serialization.Binary {

    public class BinaryParser : BinaryReader, IEnumerable<byte> {

        readonly BufferSegment buffer;
        readonly long origin;

        public BinaryParser(BufferSegment buffer) 
            : this(buffer, 0) {
        }
        public BinaryParser(BufferSegment buffer, long origin)
            : base(buffer.OpenStream()) {
            
            this.buffer = buffer;
            this.origin = origin;
        }

        public long Position {
            get { return origin + BaseStream.Position; }
        }

        public void Align(int alignment) {
            if (alignment < 2) return;

            while ((Position % alignment) > 0) ReadByte();
        }
        
        public void SkipTo(uint offset) {
            BaseStream.Seek(origin + offset, SeekOrigin.Begin);
        }

        public byte PeekByte() {
            return buffer[(uint)(BaseStream.Position)];
        }

        public string ReadStringAsciiZ(int fixedSize) {
            byte[] buffer = ReadBytes(fixedSize);
            int len = 0;

            for (len = 0; len < fixedSize; len++) {
                if (buffer[len] == 0) break;
            }

            if (len > 0) {
                return Encoding.ASCII.GetString(buffer, 0, len);
            }

            return string.Empty;
        }

        public string ReadStringAsciiZ() {
            return ReadStringZ(Encoding.ASCII);
        }

        public string ReadStringZ(System.Text.Encoding encoding) {
            using (MemoryStream ms = new MemoryStream()) {
                do {
                    ms.WriteByte(ReadByte());
                } while ((PeekChar() > -1) && (ms.GetBuffer()[ms.Length - 1] != 0));

                return encoding.GetString(ms.GetBuffer(), 0, (int)(ms.Length - 1));
            }
        }

        public T ReadRecord<T>() where T : class, new() {

            Type returnType = typeof(T);
            DynamicMethod handler = null;

            if (methodCache.ContainsKey(returnType)) {
                handler = methodCache[returnType];
            }

            if (handler == null) {

                RecordParserBuilder rbp = CreateParserBuilder(returnType);
                handler = rbp.Build();
                
                methodCache[returnType] = handler;
            }

            try {
                return handler.Invoke(null, new object[] { this }) as T;
            }
            catch (TargetInvocationException tiexc) {
                BinaryParserException bpexc = tiexc.InnerException as BinaryParserException;
                if (bpexc != null) {
                    throw bpexc;
                }

                throw;
            }
        }

        protected virtual RecordParserBuilder CreateParserBuilder(Type type) {
            return new RecordParserBuilder(this, type);
        }

        public static T GetAttribute<T>(MemberInfo mi) where T : Attribute {
            object[] attrs = mi.GetCustomAttributes(typeof(T), true);
            if ((attrs == null) || (attrs.Length == 0)) {
                return null;
            }

            return attrs[0] as T;
        }

        public static int BitCount(ulong n) {
            n = (n & 0x5555555555555555) + ((n >> 1) & 0x5555555555555555);
            n = (n & 0x3333333333333333) + ((n >> 2) & 0x3333333333333333);
            n = (n & 0x0F0F0F0F0F0F0F0F) + ((n >> 4) & 0x0F0F0F0F0F0F0F0F);

            return (int)(n % 0xFF);
        }

        public static int BitCount(uint n) {
            //n = (n & 0x55555555) + ((n >> 1) & 0x55555555);
            //n = (n & 0x33333333) + ((n >> 2) & 0x33333333);
            //n = (n & 0x0F0F0F0F) + ((n >> 4) & 0x0F0F0F0F);
            //return (int)(n % 0xFF);

            int count = 0;

            while (n > 0) {
                count++;
                n &= (n - 1);
            }

            return count;
        }

        static readonly Dictionary<Type, DynamicMethod> methodCache = new Dictionary<Type, DynamicMethod>();

        #region IEnumerable<byte> Members

        public IEnumerator<byte> GetEnumerator() {
            while (true) {
                yield return ReadByte();
            }
        }

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() {
            return this.GetEnumerator();
        }

        #endregion

    }
}
